﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using IdeaBlade.Core;
using IdeaBlade.Core.ComponentModel;
using IdeaBlade.Validation;

namespace CoProjectDF.Models.Login
{

    /// <summary>
    /// A base class providing simple validation and property changed behaviors. The
    /// DevForce VerifierEngine is used to perform validation.
    /// </summary>
    [DataContract]
    public abstract class VerifiableObject : INotifyPropertyChanged, INotifyDataErrorInfo
    {

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>
        /// Performs DevForce before/after verification on a property.
        /// </summary>
        protected bool SetProperty<T>(string propertyName, T proposedValue, Action<T> setterAction)
        {
            // When deserializing, call the base action and get out.
            if (IsDeserializing)
            {
                if (setterAction != null) { setterAction(proposedValue); }
                return true;
            }

            bool hadErrors = ClearErrors(propertyName);

            if (!VerifyBefore(propertyName, proposedValue)) { return false; }

            if (setterAction != null) { setterAction(proposedValue); }

            VerifyAfter(propertyName);

            OnPropertyChanged(propertyName);
            if (hadErrors) { OnErrorsChanged(propertyName); }

            return true;
        }

        #region Verification-related

        [Bindable(false)]
        [Display(AutoGenerateField = false)]
        public VerifierEngine VerifierEngine { get; set; }

        protected bool VerifyBefore(string propertyName, object proposedValue)
        {
            if (VerifierEngine == null) return true;

            var results = VerifierEngine.ExecuteBeforeSet(this, propertyName, proposedValue);
            if (!results.Ok)
            {
                AddErrors(results);
            }
            return results.Ok;
        }

        protected bool VerifyAfter(string propertyName)
        {
            if (VerifierEngine == null) return true;

            var results = VerifierEngine.ExecuteAfterSet(this, propertyName);
            if (!results.Ok)
            {
                AddErrors(results);
            }
            return results.Ok;
        }

        /// <summary>
        /// Can be called to perform client-side instance validation on an object.  Instance validation is not used
        /// by the simple Login model classes, but you may customize the code to provide this feature.
        /// </summary>
        /// <returns></returns>
        public bool Validate()
        {
            ClearErrors();
            var results = VerifierEngine.Execute(this);
            AddErrors(results);
            return results.Ok;
        }

        [Bindable(false)]
        [Display(AutoGenerateField = false)]
        public List<VerifierResult> ValidationErrors
        {
            get
            {
                if (_validationErrors == null)
                {
                    _validationErrors = new List<VerifierResult>();
                }
                return _validationErrors;
            }
        }

        private void AddErrors(VerifierResultCollection errors)
        {
            errors.Errors.ForEach(ValidationErrors.Add);
            var members = errors.Errors.SelectMany(vr => GetPropertyNames(vr)).Distinct();
            if (members.Count() > 0)
            {
                members.ForEach(OnErrorsChanged);
            }
        }

        public void AddError(VerifierResult result)
        {
            if (!result.IsError) return;
            ValidationErrors.Add(result);
            GetPropertyNames(result).ForEach(OnErrorsChanged);
        }

        public void RemoveError(VerifierResult result)
        {
            ValidationErrors.Remove(result);
            GetPropertyNames(result).ForEach(OnErrorsChanged);
        }

        private void ClearErrors()
        {
            ValidationErrors.Clear();
            OnErrorsChanged("");
        }

        private bool ClearErrors(string propertyName)
        {
            var remove = ValidationErrors.Where(vr => GetPropertyNames(vr).Contains(propertyName)).ToList();
            remove.ForEach(r => RemoveError(r));
            return remove.Count > 0;
        }

        private IList<string> GetPropertyNames(VerifierResult vr)
        {
            return vr.PropertyNames.Concat(
              vr.Verifier.TriggerLinks
                .Where(tl => tl.TriggerItem != null)
                .Select(tl => tl.TriggerItem.MemberName)).Distinct().ToList();
        }


        #region INotifyDataErrorInfo-related

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            List<VerifierResult> errors;

            if (String.IsNullOrEmpty(propertyName))
            {
                errors = ValidationErrors.Where(vr => !vr.Verifier.TriggerLinks.Any()).ToList();
            }
            else if (propertyName.Equals("*"))
            {
                errors = ValidationErrors.ToList();
            }
            else
            {
                errors = ValidationErrors.Where(vr => GetPropertyNames(vr).Contains(propertyName)).ToList();
            }
            return errors;
        }


        [Bindable(false)]
        [Display(AutoGenerateField = false)]
        public bool HasErrors
        {
            get { return ValidationErrors.Count > 0; }
        }

        #endregion
        #endregion

        #region Raise events

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnErrorsChanged(string propertyName)
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }
        #endregion

        #region Serialization/deserialization related
        private bool IsDeserializing = false;

        [OnDeserializing]
        internal void OnSerializing(StreamingContext context)
        {
            IsDeserializing = true;
        }

        [OnDeserialized]
        internal void OnDeserializing(StreamingContext context)
        {
            IsDeserializing = false;
        }
        #endregion


        private List<VerifierResult> _validationErrors;

    }
}
